#
# This waf script is responsible for building the SDK which can be shipped off to users into
# tintin/build/sdk/src_wscript is the file which app developers actually run to build their apps
#

import json
import os
import waflib

from string import Template
from tools.fw_elf_obfuscate import obfuscate
COPY = "cp ${SRC} ${TGT}"


def _generate_sdk_waf(ctx):
    """
    Build a custom version of waf that includes the waf plugins we need
    :param bld:
    :return:
    """
    sdk_waftools = [tool.path_from(ctx.path.parent) for tool in ctx.path.ant_glob('waftools/*.py')]
    shared_waftools = [
        "tools/resources/waftools/generate_resource_ball.py",
        "tools/resources/waftools/generate_pbpack.py",
        "tools/resources/waftools/generate_resource_id_header.py",
        "waftools/file_name_c_define.py",
        "waftools/ldscript.py",
        "waftools/objcopy.py",
        "waftools/pebble_sdk_gcc.py",
        "waftools/pebble_sdk_version.py",
        "waftools/xcode_pebble.py"
    ]

    pebble_waf_tools = []
    for tool in sdk_waftools + shared_waftools:
        path = ctx.path.parent.find_node(tool)
        if path is None:
            ctx.fatal("Trying to bundle non existent resource in pb-waf ({})".format(tool))
        pebble_waf_tools.append(path)

    # We cannot run this as a sub-wscript because we use a specific vendor-provided
    # wscript that provides the --make-waf option and needs to be run in its own clean
    # environment
    def _build_waf(task):
        bld = task.generator.bld
        cmd_str = ('cd "{}" && python "{}" distclean configure build --make-waf --tools="{}" &&'
                   'cp waf "{}"'.format(waf_folder.abspath(),
                                        task.inputs[0].abspath(),
                                        ','.join(x.abspath() for x in task.inputs[1:]),
                                        task.outputs[0].abspath()))
        try:
            bld.cmd_and_log(cmd_str, quiet=waflib.Context.BOTH)
        except waflib.Errors.WafError as e:
            bld.to_log("out: %s" % e.stdout)
            bld.to_log("err: %s" % e.stderr)
            raise e

    waf_folder = ctx.path.find_node('waf')
    waf_light = waf_folder.find_node('waf-light')
    ctx(rule=_build_waf,
        source=[waf_light, ] + pebble_waf_tools,
        target=waf_folder.get_bld())


def _copy_common_tools(bld, common_folder_node):
    """
    Copy SDK tools into common/waftools and common/tools
    :param bld:
    :param common_folder_node:
    :return:
    """
    for tool in bld.path.ant_glob(['tools/**/*']):
        bld(rule=COPY,
            source=tool,
            target=common_folder_node.make_node(tool.path_from(bld.path)))

    shared_tools = [
        "tools/binutils.py",
        "tools/bitmapgen.py",
        "tools/font/__init__.py",
        "tools/font/fontgen.py",
        "tools/generate_appinfo.py",
        "tools/generate_c_byte_array.py",
        "tools/mkbundle.py",
        "tools/pbpack.py",
        "tools/pbpack_meta_data.py",
        "tools/pebble_image_routines.py",
        "tools/pebble_sdk_platform.py",
        "tools/png2pblpng.py",
        "tools/stm32_crc.py"
    ]
    if bld.env.INTERNAL_SDK_BUILD:
        shared_tools.append("tools/pebble_sdk_platform_internal.py")

    for tool in shared_tools:
        bld(rule=COPY,
            source=bld.path.parent.find_node(tool),
            target=common_folder_node.make_node(tool))

    resource_waftools = [
        "tools/resources/__init__.py",
        "tools/resources/find_resource_filename.py",
        "tools/resources/resource_map/__init__.py",
        "tools/resources/resource_map/resource_generator.py",
        "tools/resources/resource_map/resource_generator_bitmap.py",
        "tools/resources/resource_map/resource_generator_font.py",
        "tools/resources/resource_map/resource_generator_js.py",
        "tools/resources/resource_map/resource_generator_pbi.py",
        "tools/resources/resource_map/resource_generator_png.py",
        "tools/resources/resource_map/resource_generator_raw.py",
        "tools/resources/types/__init__.py",
        "tools/resources/types/resource_ball.py",
        "tools/resources/types/resource_declaration.py",
        "tools/resources/types/resource_definition.py",
        "tools/resources/types/resource_object.py"
    ]
    for tool in resource_waftools:
        tool_node = bld.path.parent.find_node(tool)
        bld(rule=COPY,
            source=tool_node,
            target=(common_folder_node.make_node('waftools')
                    .make_node(tool_node.path_from(bld.path.parent.find_node('tools')))))


def options(opt):
    opt.add_option('--sdk_debug_elf', action='store_true',
                   help='Enable building obfuscated ELF files for SDK debugging.')


def configure(conf):
    if conf.options.sdk_debug_elf:
        conf.env.INCLUDE_SDK_DEBUG_ELF = True


def build(bld):
    bld(rule=COPY,
        source=bld.path.find_node('sdk_requirements.txt'),
        target=bld.path.get_bld().make_node('requirements.txt'))
    bld(rule=COPY,
        source=bld.path.find_node('sdk_package.json'),
        target=bld.path.get_bld().make_node('package.json'))
    bld(rule=COPY,
        source=bld.path.find_node('use_requirements.json'),
        target=bld.path.get_bld().make_node('use_requirements.json'))

    tintin_home = bld.path.parent
    platform_folder_node = bld.path.get_bld().make_node(bld.env.PLATFORM_NAME)
    platform_folder_node.parent.mkdir()

    bld(features='subst',
        source=bld.path.find_node('Doxyfile-SDK.template'),
        target=platform_folder_node.make_node('Doxyfile-SDK.auto'),
        TINTIN_ROOT=tintin_home.abspath(),
        PLATFORM_PATH=platform_folder_node.path_from(bld.path.parent))

    common_folder_node = bld.path.get_bld().make_node('common')
    common_folder_node.parent.mkdir()
    for sdk_file in bld.path.ant_glob(['include/*', 'pebble_app.ld.template']):
        bld(rule=COPY,
            source=sdk_file,
            target=common_folder_node.make_node(sdk_file.path_from(bld.path)))

    if bld.env.JS_ENGINE == 'rocky':
        js_tooling_path = os.path.dirname(bld.env.JS_TOOLING_SCRIPT.relpath())
        for js_tool in ('js_tooling.js', 'generate_snapshot.js'):
            bld(rule=COPY,
                source=bld.path.parent.get_bld().make_node(js_tooling_path).make_node(js_tool),
                target=common_folder_node.make_node('tools').make_node(js_tool),
                name='copy_rocky_tooling')

    template_folder_node = common_folder_node.make_node('templates')
    template_folder_node.parent.mkdir()

    defaults_node = bld.path.find_node('defaults')

    # Check whether the default project files are valid templates:
    with open(defaults_node.find_node('templates.json').abspath()) as f:
        templates = json.load(f)
        def _collect_check_templates_tasks(dct):
            for key in dct:
                val = dct[key]
                if isinstance(val, str):
                    # avoid unicode, it will trip up waf's Node3 and make it 💩 all over the place
                    val = str(val)
                    template_node = defaults_node.find_node(val.split(os.path.sep))
                    if not template_node:
                        waflib.Logs.warn(
                            "Could not find {}, but it's defined in "
                            "templates.json".format(val))
                        continue
                    with open(template_node.abspath()) as tf:
                        try:
                            Template(tf.read()).substitute()
                        except KeyError:
                            pass  # This is expected, no args to substitute()
                        except ValueError as e:
                            bld.fatal(
                                "Template error in {}:\n{}\n"
                                "Hint: make sure to escape dollar signs! ($ => $$)".format(
                                    template_node.abspath(), e.message))
                elif isinstance(val, dict):
                    _collect_check_templates_tasks(val)
        _collect_check_templates_tasks(templates)

    # Copy default SDK project files
    for default_file in bld.path.ant_glob('defaults/**/*'):
        bld(rule=COPY,
            source=default_file,
            target=template_folder_node.make_node(default_file.path_from(defaults_node)))

    # Generate shims
    # We shell out to this script because it imports the clang module, which does not run correctly
    # under pypy. By running python explicitly when calling this script, we avoid the
    # incompatibility with pypy and clang.
    native_generator_script = (
        bld.path.parent.find_node('tools/generate_native_sdk/generate_pebble_native_sdk_files.py'))
    export_symbols = bld.path.parent.find_node('tools/generate_native_sdk/exported_symbols.json')
    source_dir = bld.path.parent.find_node('src')
    output_source_dir = source_dir.get_bld()
    with open(export_symbols.abspath()) as f:
        native_generator_sources = (
            [source_dir.find_node(str(header)) for header in json.load(f)['files']])
    native_generator_sources.append(export_symbols)
    native_generator_targets = [bld.path.parent.make_node('src/fw/pebble.auto.c').get_bld(),
                                platform_folder_node.make_node('include/pebble.h'),
                                platform_folder_node.make_node('include/pebble_sdk_version.h'),
                                platform_folder_node.make_node('include/pebble_process_info.h'),
                                platform_folder_node.make_node('include/pebble_worker.h'),
                                platform_folder_node.make_node('include/pebble_worker_sdk_version.h')]
    bld(rule="cd '{}' ; python '{}' --sdk-dir='{}' '{}' '{}' '{}' '{}' {}".
        format(tintin_home.abspath(),
               native_generator_script.abspath(),
               platform_folder_node.abspath(),
               export_symbols.abspath(),
               source_dir.abspath(),
               output_source_dir.abspath(),
               bld.env.PLATFORM_NAME,
               '--internal-sdk-build' if bld.env.INTERNAL_SDK_BUILD else ''),
        name="generate_native_sdk",
        source=native_generator_sources,
        target=native_generator_targets)

    _generate_sdk_waf(bld)
    _copy_common_tools(bld, common_folder_node)

    # Generate our exported font header based on the whitelist in exported_symbols.json.
    # This is different than our internal header (font_resource_keys.auto.h) as it excludes
    # some fonts that we don't want to export
    def _generate_pebble_fonts_h(task):
        with open(task.outputs[0].abspath(), 'w') as f_out:
            f_out.write('#pragma once\n')
            f_out.write('\n')

            with open(task.inputs[0].abspath(), 'r') as f_in:
                font_list = json.load(f_in)["fonts"]

            for font in font_list:
                f_out.write('#define FONT_KEY_{0} "RESOURCE_ID_{0}"\n'.format(font))

    # Copy any font keys over to the SDK
    bld(rule=_generate_pebble_fonts_h,
        source=export_symbols,
        target=platform_folder_node.make_node('include/pebble_fonts.h'))

    # Generate obfuscated elf file for GDB debugging
    if bld.env.INCLUDE_SDK_DEBUG_ELF:
        def _obfuscate_elf(task):
            input_elf = task.inputs[0].abspath()
            output_elf = task.outputs[0].abspath()
            obfuscate(input_elf, output_elf, no_text=False)

        firmware_build_node = bld.path.parent.get_bld().find_or_declare('src').find_or_declare('fw')
        bld(rule=_obfuscate_elf,
            source=firmware_build_node.make_node('tintin_fw.elf'),
            target=bld.path.get_bld().make_node('{}_sdk_debug.elf'.format(bld.env.PLATFORM_NAME)))
